﻿using System;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Collections;
using System.Net;
using SocketCore.Network;
using System.ComponentModel;
using Logger;
using SocketCore.Encoder;
using SocketCore.Decoder;
using System.IO;
using SocketCore.Event;


namespace SocketCore
{
    public class SocketClient
    {
        #region delegates and events

        public delegate void ReceiveHandler(object sender, Message msg);
        public delegate void ConnectHandler(object sender, bool bSuccess);
        public delegate void DisconnectHandler(object sender);

        public event ReceiveHandler Received;
        public event ConnectHandler Connected;
        public event DisconnectHandler Disconnected;

        #endregion

        #region private members 

        private const int SleepPeriod = 10; // sleep for 10 miliseconds

        private Queue TransmitQueue = new Queue();
        private Queue ReceiveQueue = new Queue();
        private ReaderWriterLock TransmitLock = new ReaderWriterLock();
        private ReaderWriterLock ReceiveLock = new ReaderWriterLock();
        private bool _IsConnected = false;
        private Socket _Socket;

        private ManualResetEvent ReaderDone = new ManualResetEvent(false);
        private ManualResetEvent WriterDone = new ManualResetEvent(false);
        private ManualResetEvent StopEvent = new ManualResetEvent(false);
        private AutoResetEvent DataReady = new AutoResetEvent(false);

        private long StartTime;

        private IMessageEncoder m_iMessEncode = MessageEncoderFactory.Instance.GetMessageEncoder(0);

        private IMessageDecoder m_iMessDecode = MessageDecoderFactory.Instance.GetMessageDecoder(0);

        private MemoryStream messageBufferStream;

        private Timer m_Timer;

        private DateTime m_LastServerAccess;

        private const int MAX_WAIT_HEART_BEAT_DISCONNECT = 20000;

        #endregion

        #region properties

        public bool IsConnected
        {
            get { return (_Socket == null ? false : _Socket.Connected); }
        }

        #endregion

        #region constructor and destructor

        public SocketClient()
        {
            StartTime = DateTime.Now.Ticks;
           
            
        }

        ~SocketClient()
        {
            if(IsConnected)
                Disconnect();
        }

        #endregion

        #region public interface

        public int Connect(string hostName, int serviceport)
        {
            if (IsConnected && !_IsConnected)
                return -1; //'no need to do anything once connected

            // resolve...
            IPHostEntry hostEntry = Dns.GetHostEntry(hostName);
            if (hostEntry != null)
            {
                // create an end-point for the first address...
                IPAddress add = null;
                foreach(IPAddress a in hostEntry.AddressList)
                {
                    if (a.AddressFamily == AddressFamily.InterNetwork)
                    {
                        add = a;
                        break;
                    }
                }
                if (add == null)
                    add = hostEntry.AddressList[0];
                IPEndPoint endPoint = new IPEndPoint(add, serviceport);

               

                _Socket = new Socket(add.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                try
                {
                    _Socket.Connect(endPoint);
                }
                catch (System.Exception ex)
                {
                    OnConnect(this, false);
                    return -1;
                }

                OnConnect(this, true);

                m_Timer = new Timer(new TimerCallback(OnPingHeartBeatTimer), this, 3000, 3000);
                m_LastServerAccess = DateTime.Now;
                StopEvent.Reset();
                ThreadPool.QueueUserWorkItem(new WaitCallback(SendThreadEntryPoint));
                _IsConnected = true;

                ReadMessage();
                
                // OK!
                return 0;
            }
            else
            {
                return -1;
            }
        }

        public void Disconnect()
        {
            if (_IsConnected)
            {
                m_Timer.Dispose();
                _IsConnected = false;

                // signal the threads to end
                StopEvent.Set();

                // now kill the socket
                if (_Socket != null)
                {
                    _Socket.Close();
                }
                OnDisconnect(this);
            }
            
        }

        public void SendMessage(Message message)
        {
            // queue it...
            TransmitLock.AcquireWriterLock(-1);
            try
            {
                TransmitQueue.Enqueue(message);
            }
            catch { }
            finally { TransmitLock.ReleaseWriterLock(); }

            // signal that data was sent
            DataReady.Set();
        }

        #endregion

        #region protected members

        protected void OnReceive(object sender, Message msg)
        {
            if (Received != null)
            {
                object[] param = new object[2] { sender, msg };
                SafeFireEvent(Received, ref param);
            }
        }

        protected void OnConnect(object sender, bool bSuccess)
        {
            if (Connected != null)
            {
                object[] param = new object[2] { sender, bSuccess };
                SafeFireEvent(Connected, ref param);
            }
        }

        protected void OnDisconnect(object sender)
        {
            if (Disconnected != null)
            {
                object[] param = new object[1] { sender };
                SafeFireEvent(Disconnected, ref param);
            }
        }

        #endregion

        #region private send/receive threads

        private void OnPingHeartBeatTimer(object obj)
        {
            DateTime dt = DateTime.Now;
            if ((dt - m_LastServerAccess).TotalMilliseconds > MAX_WAIT_HEART_BEAT_DISCONNECT)
            {
                if (IsConnected)
                {
                    Disconnect();
                }    
            }           
        }

        private void OnMessageReceive(object state)
        {
            MessageEvent msgEvent = (MessageEvent)state;
            if(msgEvent.Message.Type != PackageTypeEnum.Heartbeat)
            {
                OnReceive(this, msgEvent.Message);
            }
            else
            {
                SendMessage(msgEvent.Message);
            }

            m_LastServerAccess = DateTime.Now;

        }

        private byte[] GetDataGram(Message mess)
        {
            byte[] data = m_iMessEncode.Encode(mess, null);
            int len = data.Length;
            byte[] datagram = new byte[NetworkConfig.DatagramHeaderLength + len];
            for (int i = NetworkConfig.DatagramHeaderLength - 1; i > -1; i--)
            {
                datagram[i] = (byte)len;
                len = len >> 8;
            }
            Array.Copy(data, 0, datagram, NetworkConfig.DatagramHeaderLength, data.Length);
            return datagram;
        }

        private void ProcessBody(IAsyncResult ar)
        {
            try
            {
                if (this.IsConnected)
                {
                    byte[] readBytes = (byte[])ar.AsyncState;
                    int byteRead = _Socket.EndReceive(ar);
                    if (byteRead != 0)
                    {
                        this.messageBufferStream.Write(readBytes, 0, byteRead);
                    }
                    else
                    {
                        Disconnect();
                        return;
                    }
                    long remaining = readBytes.Length - byteRead;
                    if (remaining > 0L)
                    {
                        this.ReadBuffer(remaining, 1);
                    }
                    else
                    {
                        this.messageBufferStream.Position = 0L;
                        try
                        {
                            Message message = m_iMessDecode.Decode(messageBufferStream);
                            if (message == null)
                            {
                                Disconnect();
                                return;
                            }

                            ThreadPool.QueueUserWorkItem(new WaitCallback(OnMessageReceive), new MessageEvent(null, message));
                        }
                        catch (System.Exception e)
                        {
                            Disconnect();
                        }
                        this.ReadMessage();
                    }
                }
                else
                {
                    Disconnect();
                }
            }
            catch (System.Exception e)
            {
                Disconnect();
            }
        }

        private void ProcessHeader(IAsyncResult ar)
        {
            try
            {
               
                if (this.IsConnected)
                {
                    int byteRead = _Socket.EndReceive(ar);
                    byte[] readBytes = (byte[])ar.AsyncState;
                    if (byteRead != 0)
                    {
                        this.messageBufferStream.Write(readBytes, 0, byteRead);
                    }
                    else
                    {
                       Disconnect();
                        return;
                    }
                    long remaining = 4L - this.messageBufferStream.Length;
                    if (remaining > 0L)
                    {
                        this.ReadBuffer(remaining, 0);
                    }
                    else
                    {
                        this.messageBufferStream.Position = 0L;
                        int mailSize = ReadInt(this.messageBufferStream);
                        this.messageBufferStream.Close();
                        this.messageBufferStream = new MemoryStream();
                        this.ReadBuffer((long)mailSize, 1);
                    }
                }
                else
                {
                    Disconnect();
                }
            }
            catch (System.Exception e)
            {
                Disconnect();
            }
        }

        private void ReadBuffer(long remainLen, int type)
        {
            try
            {
                if (this.IsConnected)
                {
                    byte[] buffer = new byte[remainLen];
                    AsyncCallback callBack = null;
                    switch (type)
                    {
                        case 0:
                            callBack = new AsyncCallback(this.ProcessHeader);
                            break;

                        case 1:
                            callBack = new AsyncCallback(this.ProcessBody);
                            break;

                        default:
                            throw new ArgumentException("Cannot recognize type: " + type);
                    }
                   
                   _Socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, callBack, buffer);
                }
            }
            catch (System.Exception e)
            {
                Disconnect();
           
            }
        }

        private static int ReadInt(Stream stream)
        {
            int i = 0;
            for (int j = 0; j < 4; j++)
            {
                i = i << 8;
                int readByte = stream.ReadByte();
                i |= readByte;
            }
            return i;
        }

        private void ReadMessage()
        {
            try
            {
                if (this.IsConnected)
                {
                    if (this.messageBufferStream != null)
                    {
                        try
                        {
                            this.messageBufferStream.Close();
                        }
                        catch
                        {
                        }
                    }
                    messageBufferStream = new MemoryStream();
                    ReadBuffer(4L, 0);
                }
                else
                {
                    Disconnect();
                }
            }
            catch (System.Exception e)
            {
                Disconnect();
       
            }
        }



        private void SendThreadEntryPoint(object state)
        {
            try
            {
                Queue workQueue = new Queue();

                // loop...
                while (true)
                {
                    WaitHandle[] handles = new WaitHandle[2];
                    handles[0] = StopEvent;
                    handles[1] = DataReady;

                    if (WaitHandle.WaitAny(handles) == 0)
                    {
                        break;
                    }
                    else if (_Socket != null && _Socket.Connected)
                    {
                        // not disconnected
                        // go through the queue...
                        TransmitLock.AcquireWriterLock(-1);
                        try
                        {
                            workQueue.Clear();
                            foreach (Message message in TransmitQueue)
                            {
                                workQueue.Enqueue(message);
                            }
                            TransmitQueue.Clear();
                        }
                        catch { }
                        finally
                        {
                            TransmitLock.ReleaseWriterLock();
                        }

                        // loop the outbound messages...
                        foreach (Message message in workQueue)
                        {
                            //SocketStateObject so2 = new SocketStateObject(_Socket);
                            byte[] buff = GetDataGram(message);
                               
                            // send it...
                            System.IAsyncResult iar;
                            iar = _Socket.BeginSend(buff, 0, buff.Length, 0, new AsyncCallback(AsynchSendCallback), buff);
                        }

                    }
                }
            }
            catch
            {
                Disconnect();
            }
        }

        private void AsynchSendCallback(System.IAsyncResult ar)
        {
            //SocketStateObject so = (SocketStateObject)ar.AsyncState;
            //Socket s = so.WorkSocket;
            try
            {
                // sanity check
                if (_Socket == null || !_Socket.Connected) return;
                int send = _Socket.EndSend(ar);
            }
            catch
            {
                Disconnect();
            }
        }


        public static void SafeFireEvent(Delegate handler, ref  object[] args)
        {
            if (handler != null)
            {
                foreach (System.Delegate singleCast in handler.GetInvocationList())
                {
                    try
                    {
                        if (singleCast.Target is ISynchronizeInvoke)
                        {
                            ISynchronizeInvoke syncInvoke = (ISynchronizeInvoke)singleCast.Target;
                            if (syncInvoke.InvokeRequired)
                            {
                                syncInvoke.Invoke(singleCast, args);
                            }
                            else
                                singleCast.DynamicInvoke(args);
                        }
                        else
                            singleCast.DynamicInvoke(args);
                    }
                    catch (System.Exception ex)
                    {
                        Log.WriteLog(ex);
                    }
                }
            }
        }

        #endregion
    }
}
